package com.zhangfb95.crosschain.server.service.impl;

import com.zhangfb95.crosschain.infra.util.tls.client.ReactorNettyWebClientBuilder;
import com.zhangfb95.crosschain.infra.util.tls.client.WebClientSslProperties;
import com.zhangfb95.crosschain.protocol.exc.ExcAccountService;
import com.zhangfb95.crosschain.protocol.exc.ExcChainService;
import com.zhangfb95.crosschain.protocol.exc.ExcService;
import com.zhangfb95.crosschain.protocol.exc.ExcServiceRegistry;
import com.zhangfb95.crosschain.protocol.exc.request.GetAccountExcReq;
import com.zhangfb95.crosschain.protocol.exc.request.GetBlockByNumberExcReq;
import com.zhangfb95.crosschain.protocol.exc.request.GetHeightExcReq;
import com.zhangfb95.crosschain.protocol.exc.request.GetTxByIdExcReq;
import com.zhangfb95.crosschain.protocol.exc.request.QueryExcReq;
import com.zhangfb95.crosschain.protocol.exc.request.SendExcReq;
import com.zhangfb95.crosschain.protocol.exc.request.SignExcReq;
import com.zhangfb95.crosschain.protocol.exc.response.GetAccountExcRes;
import com.zhangfb95.crosschain.protocol.exc.response.GetBlockByNumberExcRes;
import com.zhangfb95.crosschain.protocol.exc.response.GetHeightExcRes;
import com.zhangfb95.crosschain.protocol.exc.response.GetTxByIdExcRes;
import com.zhangfb95.crosschain.protocol.exc.response.ListChainPathExcRes;
import com.zhangfb95.crosschain.protocol.exc.response.QueryExcRes;
import com.zhangfb95.crosschain.protocol.exc.response.SendExcRes;
import com.zhangfb95.crosschain.protocol.exc.response.SignExcRes;
import com.zhangfb95.crosschain.server.controller.ExcAccountController;
import com.zhangfb95.crosschain.server.infra.config.properties.ExcProperties;
import com.zhangfb95.crosschain.server.infra.config.properties.WebServerProperties;
import com.zhangfb95.crosschain.server.infra.data.api.exc.ExcAccountApi;
import com.zhangfb95.crosschain.server.infra.data.api.exc.ExcChainApi;
import com.zhangfb95.crosschain.server.infra.model.service.dto.ChainInfo;
import com.zhangfb95.crosschain.server.infra.model.service.dto.ChainInfoHolder;
import com.zhangfb95.crosschain.server.service.ChainService;
import com.zhangfb95.crosschain.server.service.CrossChainService;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.support.WebClientAdapter;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * @author zhangfubing
 * @since 2023/10/20
 */
@Slf4j
@Service
public class CrossChainServiceImpl implements CrossChainService {

    private final ChainInfoHolder chainInfoHolder = new ChainInfoHolder();
    private final ExcServiceRegistry excServiceRegistry = new ExcServiceRegistry();

    @Autowired
    private ExcProperties excProperties;
    @Autowired
    private WebServerProperties webServerProperties;
    @Autowired
    private ChainService chainService;
    @Autowired
    private ExcAccountController excAccountController;

    @PostConstruct
    public void init() {
        for (ExcProperties.Item item : excProperties.getList()) {
            WebClientSslProperties sslProperties = new WebClientSslProperties()
                    .setServerUrl(item.getUrl())
                    .setSslType(item.getSslType())
                    .setCaCert(item.getCaCert())
                    .setSslKey(item.getSslKey())
                    .setSslCert(item.getSslCert());
            WebClient webClient = ReactorNettyWebClientBuilder.build(sslProperties);
            HttpServiceProxyFactory httpServiceProxyFactory = HttpServiceProxyFactory
                    .builder(WebClientAdapter.forClient(webClient))
                    .build();
            ExcAccountApi excAccountApi = httpServiceProxyFactory.createClient(ExcAccountApi.class);
            ExcChainApi excChainApi = httpServiceProxyFactory.createClient(ExcChainApi.class);
            excServiceRegistry.register(item.getUrl(), new ExcService(excAccountApi, excChainApi));
        }
    }

    @Override
    public void syncChainInfos() {
        // 获取本地服务id
        String localServerId = webServerProperties.getServerId();
        // 获取本地链路径列表
        List<String> localChainPaths = chainService.listChainPath();

        // 获取远程服务id和远程链路径列表
        Map<String, ListChainPathExcRes> url2ResMap = new HashMap<>();
        excServiceRegistry.getExcChainServices().forEach((url, excChainService) -> {
            try {
                ListChainPathExcRes apiRes = excChainService.listChainPath().fallback().getData();
                log.info("from exc server msg, address: {}, apiRes: {}", url, apiRes);
                url2ResMap.put(url, apiRes);
            } catch (Exception e) {
                log.error("from exc server err, address: {}, message: {}", url, e.getMessage(), e);
            }
        });

        chainInfoHolder.updateChainInfo(localServerId, localChainPaths, url2ResMap);
    }

    @Override
    public GetAccountExcRes getAccount(String serverId, GetAccountExcReq req) {
        if (Objects.equals(webServerProperties.getServerId(), serverId)) {
            return excAccountController.getAccount(req).fallback().getData();
        } else {
            String serverUrl = chainInfoHolder.getServerUrl(serverId);
            ExcAccountService excAccountService = excServiceRegistry.getExcAccountService(serverUrl);
            return excAccountService.getAccount(req).fallback().getData();
        }
    }

    @Override
    public SignExcRes sign(String serverId, SignExcReq req) {
        if (Objects.equals(webServerProperties.getServerId(), serverId)) {
            return excAccountController.sign(req).fallback().getData();
        } else {
            String serverUrl = chainInfoHolder.getServerUrl(serverId);
            ExcAccountService excAccountService = excServiceRegistry.getExcAccountService(serverUrl);
            return excAccountService.sign(req).fallback().getData();
        }
    }

    @Override
    public GetHeightExcRes getHeight(GetHeightExcReq req) {
        ChainInfo chainInfo = chainInfoHolder.getChainInfo(req.getChainPath());
        if (chainInfo.isLocal()) {
            return chainService.getHeight(req);
        } else {
            ExcChainService excChainService = excServiceRegistry.getExcChainService(chainInfo.getServerUrl());
            return excChainService.getHeight(req).fallback().getData();
        }
    }

    @Override
    public GetBlockByNumberExcRes getBlockByNumber(GetBlockByNumberExcReq req) {
        ChainInfo chainInfo = chainInfoHolder.getChainInfo(req.getChainPath());
        if (chainInfo.isLocal()) {
            return chainService.getBlockByNumber(req);
        } else {
            ExcChainService excChainService = excServiceRegistry.getExcChainService(chainInfo.getServerUrl());
            return excChainService.getBlockByNumber(req).fallback().getData();
        }
    }

    @Override
    public GetTxByIdExcRes getTxById(GetTxByIdExcReq req) {
        ChainInfo chainInfo = chainInfoHolder.getChainInfo(req.getChainPath());
        if (chainInfo.isLocal()) {
            return chainService.getTxById(req);
        } else {
            ExcChainService excChainService = excServiceRegistry.getExcChainService(chainInfo.getServerUrl());
            return excChainService.getTxById(req).fallback().getData();
        }
    }

    @Override
    public QueryExcRes query(QueryExcReq req) {
        ChainInfo chainInfo = chainInfoHolder.getChainInfo(req.getChainPath());
        if (chainInfo.isLocal()) {
            return chainService.query(req);
        } else {
            ExcChainService excChainService = excServiceRegistry.getExcChainService(chainInfo.getServerUrl());
            return excChainService.query(req).fallback().getData();
        }
    }

    @Override
    public SendExcRes send(SendExcReq req) {
        ChainInfo chainInfo = chainInfoHolder.getChainInfo(req.getChainPath());
        if (chainInfo.isLocal()) {
            return chainService.send(req);
        } else {
            ExcChainService excChainService = excServiceRegistry.getExcChainService(chainInfo.getServerUrl());
            return excChainService.send(req).fallback().getData();
        }
    }
}
